package implementations;

import interfaces.ConnectionPool;
import interfaces.ConnectionSource;

import java.util.concurrent.*;

public class MyConnectionPool<T> implements ConnectionPool<T> {

    private ConnectionSource<T> connectionSource;
    private int min, max;
    private long keepAliveTime;
    private TimeUnit timeUnit;

    private Semaphore semaphore;
    private BlockingQueue<MyConnectionSource<T>> connections;
    private Thread delayThread;

    public MyConnectionPool(int min, int max, long keepAliveTime, TimeUnit timeUnit, ConnectionSource<T> connectionSource) {
        this.connectionSource = connectionSource;
        this.min = min;
        this.max = max;
        this.keepAliveTime = keepAliveTime;
        this.timeUnit = timeUnit;

        semaphore = new Semaphore(max, true);
        connections = new DelayQueue<>();

        initializePool(min, keepAliveTime, connectionSource);
    }

    /**
     * Pre-fill connection queue if necessary, launch cleaning thread
     * @param min minimum of alive connections
     * @param keepAliveTime after that delay unused connections will be released
     * @param connectionSource resource to use
     */
    private void initializePool(int min, long keepAliveTime, ConnectionSource<T> connectionSource) {
        for (int i = 0; i < min; i++) {
            connections.offer(new MyConnectionSource<>(connectionSource.open(), keepAliveTime, TimeUnit.MILLISECONDS));
        }

        delayThread = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (connections.size() > min){
                    connections.poll();
                }
            }
        });
        delayThread.setDaemon(true);
        delayThread.start();
    }

    /**
     *Try to open resource if necessary and give it from queue
     * @return <T> connection
     */
    private T getResource() {
        if (connections.isEmpty()) {
            connections.offer(new MyConnectionSource<>(connectionSource.open(), keepAliveTime, timeUnit));
        }
        MyConnectionSource<T> cs = connections.peek();
        connections.remove(cs);
        return cs.getConnection();
    }

    /**
     * Blocking version of function
     * @return resource
     * @throws InterruptedException
     */
    @Override
    public T get() throws InterruptedException{
        semaphore.acquire();
        return getResource();
    }

    /**
     * Non-blocking version of function
     * @return resource or null if there is not free resource
     */
    @Override
    public T tryGet() {
        if (semaphore.tryAcquire(1)) {
            return getResource();
        }
        return null;
    }

    /**
     * Blocking version of function with timeout
     * @param time delay for waiting if any resource is free
     * @param unit time unit for delay
     * @return resource or null if after timeout there is still no free resource
     */
    @Override
    public T tryGet(long time, TimeUnit unit) throws InterruptedException{
        if (semaphore.tryAcquire(time, unit)) {
            return getResource();
        }
        return null;
    }

    /**
     * Free resource and return it to pool
     * @param src resource
     */
    @Override
    public void release(T src) {
        connections.offer(new MyConnectionSource<>(src, keepAliveTime, timeUnit));
        semaphore.release();
    }

    /**
     * Release all resources if something goes wrong
     * @throws Throwable
     */
    @Override
    protected void finalize() throws Throwable {
        for (MyConnectionSource<T> connection : connections) {
            connectionSource.close(connection.getConnection());
        }
        connections.clear();
        super.finalize();
    }
}
